import { MongoClient, MongoError, TransactionOptions } from "mongodb"

const uri = "mongodb+srv://test:dev1024.@aaron.k7jia.mongodb.net/?retryWrites=true&w=majority&appName=Aaron"

const client = new MongoClient(uri)
// 购买东西 （便携事务）
async function buySomething(order: any) {
  // 创建会话
  const txnResult = await client.withSession(async (session) => {
    // 开启事务
    session.withTransaction(async (session) => {
      // 获取库存集合、订单集合
      const invColl = client.db('testdb').collection('inventory')
      const recColl = client.db('testdb').collection('orders')
      // 查询库存是否足够
      let total = 0
      for (const item of order) {
        // 查找 item 相等，qty字段大于 item.qty, 找到后，将 qty 减去购买数量（item.qty）
        const inStock = await invColl.findOneAndUpdate({
          item: item.item,
          qty: { $gte: item.qty }
        },
          { $inc: { qty: -item.qty } }, { session })

        // 库存不足，关闭会话
        if (inStock === null) {
          await session.abortTransaction()
          return "Item not found or insufficient quantity."
        }
        const subTotal = item.qty * inStock.price
        total += subTotal
      }

      // 创建购买记录
      const receipt = {
        date: new Date(),
        items: order,
        total: total
      }
      await recColl.insertOne(receipt, { session })
      return (
        "Order successfully completed and recorded!\nReceipt:\n" +
        JSON.stringify(receipt, null, 1)
      )
    })
  })
}

// 购买东西（标准事务）
async function placeOrder(cart: any, payment: any) {

  // 创建会话
  const session = client.startSession()

  // 创建事务
  try {
    const transactionOptions: TransactionOptions = {
      readConcern: { level: 'snapshot' },
      writeConcern: { w: 'majority' },
      readPreference: 'primary'
    }
    session.startTransaction(transactionOptions)

    // 获取订单、库存、用户集合
    const database = client.db('testdb')
    const ordersCollection = database.collection('orders')
    const inventoryCollection = database.collection('inventory')
    const customerCollection = database.collection('customers')

    const orderResult = await ordersCollection.insertOne(
      {
        customer: payment.customer,
        items: cart,
        total: payment.total
      },
      { session }
    )

    // 查询库存是否足够
    for (const item of cart) {
      const inStock = await inventoryCollection.findOneAndUpdate(
        {
          item_id: item.item_id,
          item_id: { $gte: item.qty }
        },
        { $inc: { 'qty': -item.qty } },
        { session }
      )
      if (inStock === null) {
        // 库存不足，终止事务，关闭会话
        await session.abortTransaction()
        await session.endSession()
        return "Item not found or insufficient quantity."
      }
    }

    await customerCollection.updateOne(
      { _id: payment.customer },
      { $push: { orders: orderResult.insertedId } } as any,
      { session }
    )
    // 提交事务
    await session.commitTransaction()
    console.log('Transaction successfully committed.')
  } catch (error) {
    if (error instanceof MongoError && error.hasErrorLabel('UnknownTransactionCommitResult')) {
      // Add your logic to retry or handle the error
    }
    else if (error instanceof MongoError && error.hasErrorLabel('TransientTransactionError')) {
      // Add your logic to retry or handle the error
    } else {
      console.log('An error occured in the transaction, performing a data rollback:' + error)
    }
    await session.abortTransaction()
  } finally {
    await session.endSession()
  }
}


const cart = [
  { item: 'sunblock', item_id: 5432, qty: 1, price: 5.19 },
  { item: 'beach towel', item_id: 7865, qty: 2, price: 15.99 }
]
const payment = { customer: 98765, total: 37.17 }

buySomething(cart)